/*
 *
 * ndmp_hba.c defines the attributes and the functions of the NDMP initiator.
 * The function here is the ndmp_scsi_queuecommand which queues commands from SCSI midlayer
 * The NDMP thread (ndmp_thread.c) would take the commands from the queue and service it.
 *
 *
 *      Author: Aboo Valappil
 *      Date: 2/10/2008
 *      Based on: iSCSI initiator
 */

#include "ndmp_main.h"

extern struct kmem_cache *ndmp_scsi_task_cache;

static int ndmp_scsi_eh_device_reset(struct scsi_cmnd *sc);
static int ndmp_scsi_eh_host_reset(struct scsi_cmnd *sc);
static int  ndmp_scsi_queuecommand(struct scsi_cmnd *sc, void (*done) (struct scsi_cmnd *));
static int ndmp_scsi_eh_abort(struct scsi_cmnd *sc);


/*
 *
 * ndmp_scsi_eh_abort - A routine called from SCSI MID layer to abort a command when not progressing.
 * This command may not be called at all as the watchdog should take care of it.
 *
 */
static int ndmp_scsi_eh_abort(struct scsi_cmnd *sc)
{

	struct Scsi_Host *shost = sc->device->host;
	struct ndmp_scsi_session *session = (struct ndmp_scsi_session *)shost->hostdata;
	struct ndmp_scsi_task *task;

	spin_lock(&session->task_lock);

	task = (struct ndmp_scsi_task *)sc->SCp.ptr;
	if (!task) goto done;
	
	printk( "ndmphba:%d: Aborting command sequence number : %x\n", session->session_id,task->seq_number);
	task->flags=NDMP_SCSI_TASK_ABORTED;
	ndmp_scsi_complete_task(task);
done:
	spin_unlock(&session->task_lock);
	return SUCCESS;
	
}

/*
 *  ndmp_scsi_eh_device_reset - A routine called from MID layer. This routine does nothing at the moment.
 *  I think this should be imporved to close the device and reconnect and re-open. 
 *  But, once the device stops responding, there is no guarantee that the above operation would work.
 *  Just leaving this routine like this for time being.
 */
static int ndmp_scsi_eh_device_reset(struct scsi_cmnd *sc) {
	struct Scsi_Host *shost = sc->device->host;
        struct ndmp_scsi_session *session = (struct ndmp_scsi_session *)shost->hostdata;
	printk( "ndmphba:%d: In Reset Device\n",session->session_id);
	return SUCCESS;
}

/* 
 *
 *  ndmp_scsi_eh_host_reset - This routine aborts all the commands on the initiator, called from MID layer.
 *  Since this is single threaded design, there will be only one command pending.
 *  The abort will be done by the watchdog anyway, and I do not think the control will get here any time
 */
static int ndmp_scsi_eh_host_reset(struct scsi_cmnd *sc) {
	struct Scsi_Host *shost = sc->device->host;
        struct ndmp_scsi_session *session = (struct ndmp_scsi_session *)shost->hostdata;
	struct ndmp_scsi_task *task,*tmp;

	printk( "ndmphba:%d: In Reset Host, Aborting all the commands Queued\n",session->session_id);

	spin_lock(&session->task_lock);
	list_for_each_entry_safe(task, tmp, &session->task_queue, queue) {
		task->flags=NDMP_SCSI_TASK_ABORTED;
		ndmp_scsi_complete_task(task);
		
	}
	spin_unlock(&session->task_lock);
	
	return SUCCESS;
}

/*
 *
 * ndmp_set_sensedata_commfailure - Sets the sense data to not ready when the NDMP session is not ready
 * The session will not be ready if the connection is not made, the NDMP device is not open or the NDMP thread halted.
 */
void ndmp_set_sensedata_commfailure(struct scsi_cmnd *sc)
{
        sc->sense_buffer[0] = 0xf0;
        sc->sense_buffer[2] = NOT_READY;
        sc->sense_buffer[7] = 0x6;
        sc->sense_buffer[12] = 0x08; // Lun Communication failure
        sc->sense_buffer[13] = 0x00;
}

/*
 *
 * ndmp_set_sensedata_illegal - Sets the sense data to a illegal request.
 * Mainly called when the MID layer issues any commands other than LUN 0 as NDMP initiator only supports LUN 0.
 *
 */
void ndmp_set_sensedata_illegal(struct scsi_cmnd *sc) {

	memset(sc->sense_buffer,0,SCSI_SENSE_BUFFERSIZE);
        sc->sense_buffer[0] = 0xf0;
        sc->sense_buffer[2] = 0;   // No sense
        sc->sense_buffer[7] = 0x6;
        sc->sense_buffer[12] = 0;  // No Sense;
        sc->sense_buffer[13] = 0x00;
}

/*
 *
 * ndmp_scsi_queuecommand - The queue command called from the SCSI MID layer.
 * @sc - SCSI command to be queued
 * @done - The completion routine to be called after completing the task
 *
 */
static int  ndmp_scsi_queuecommand(struct scsi_cmnd *sc, void (*done) (struct scsi_cmnd *))
{
        struct Scsi_Host *host = sc->device->host;
        struct ndmp_scsi_session *session = (struct ndmp_scsi_session *)host->hostdata;
        struct ndmp_scsi_task *task;


/* The host_lock is locked up on entry to this function. I am not sure why we have to unlock it before doing things. 
 * The idea I got to unlock is from the iscsi initiator code and I did not do much of research on this, 
 * just leave as it is as things seems to be working :) 
 */
	spin_unlock_irq(host->host_lock);

/* Yea, lock the session before entering things in to the command queue. */

	spin_lock(&session->task_lock);

/* Only queue the command if the device is open and the NDMP thread is running */
	if ( (__ndmp_session_state(session,HBA_STATE_DEVICE) != _NDMP_STAT_DONE ) || ( __ndmp_session_state(session,HBA_STATE_THREAD) != _NDMP_STAT_DONE ) ) {
			spin_unlock(&session->task_lock);
			spin_lock_irq(host->host_lock);
			sc->result = DID_NO_CONNECT << 16;
        		scsi_set_resid(sc, scsi_bufflen(sc));
        		ndmp_set_sensedata_commfailure(sc);
        		done(sc);
	        	return 0;
	}

        sc->scsi_done = done;
        sc->result = 0;
        memset(&sc->SCp, 0, sizeof(sc->SCp));

/* allocates a command structure from the slab */
        task = ndmp_scsi_alloc_task(session);

        if (!task) {
		spin_unlock(&session->task_lock);
		spin_lock_irq(host->host_lock);
                return SCSI_MLQUEUE_HOST_BUSY;
        }

/* Set the flag to submitted so that the NDMP thread will know this command is for processing. */
	task->flags=NDMP_SCSI_TASK_SUBMITTED;
	task->sc = sc;

	sc->SCp.ptr = (char *)task;

/* Add the command to the queue  for NDMP thread processing*/	
	list_add_tail(&task->queue, &session->task_queue);

/* Unlock / lock things what we have unlocked and locked */	
	spin_unlock(&session->task_lock);
	spin_lock_irq(host->host_lock);

/* Wake up the NDMP thread as one command is pending now! */
	wake_up_interruptible(&session->txq);

	return 0;

}


/* 
 * Now that all the SCSI Midlayer functins are defined, initialize the template to these routines
 */
struct scsi_host_template ndmp_scsi_host_template = {
        .name = "ndmphba",
        .proc_name = NDMP_SCSI_PROC_NAME,
        .module = THIS_MODULE,
        .queuecommand = ndmp_scsi_queuecommand,
        .eh_abort_handler = ndmp_scsi_eh_abort,
        .eh_device_reset_handler = ndmp_scsi_eh_device_reset,
        .eh_host_reset_handler = ndmp_scsi_eh_host_reset, 

        .this_id = -1,

/* These values are from the ndmp_main.h, 
 *  Like how many commands can be queued, maximum data transfer size, maximum LUNs, etc.
 */
        .can_queue = NDMP_SCSI_CANQUEUE,
        .sg_tablesize=NDMP_SCSI_MAX_SG,
        .cmd_per_lun = NDMP_SCSI_CMDS_PER_LUN,
        .use_clustering = ENABLE_CLUSTERING,
        .max_sectors = NDMP_SCSI_MAX_SECTORS,

        .emulated = 1,
};
